package mini.expr.lists;
import java.util.HashMap;
import java.util.LinkedList;

import mini.Proc;
import mini.expr.Element;
import mini.expr.Expr;
import mini.memory.MemoryManager;


public class HeapCons extends Expr {
	private static int counter = 0; 
    private Expr e1;
    private Expr e2;
    private MemoryManager memoryManager = MemoryManager.getInstance();    
    
    public HeapCons(Expr e1, Expr e2) {
        this.e1 = e1;
        this.e2 = e2;
    }

    public Element eval(HashMap<String, Element> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
    	HashMap<String, Element> nt = new HashMap<String, Element>();
    	
    	//Copy the environment
        for(String key : nametable.keySet()) {
            nt.put(key, nametable.get(key));
        }
        
        Element car = e1.eval(nt, functiontable, var);
        Element cdr = null;
        
        if (car != null && car.isList()) {
        	nt.put(String.format("consTmpVar%d", counter++), car);
        }
        
        if (e2 == null) {
            // New list 
            cdr = new Element(new ElementList());
        } else {
            cdr = e2.eval(nt, functiontable, var);
        }
        
        if (!cdr.isList()) {
            throw new IllegalStateException("Expression does not evaluate to a list.");
        }
        
        nt.put(String.format("consTmpVar%d", counter++), cdr);
        
        Integer existingMemAddress = cdr.getListValue().getSequence().getMemoryAddress();
        Integer memAddress = memoryManager.store(car, existingMemAddress);
        
        //check if the value was stored
        if (memAddress == null) {
            //Try garbage collection
            memoryManager.garbageCollect(nt);
            
            //Now try again to allocate memory for the element
            memAddress = memoryManager.store(car, existingMemAddress);
            
            if (memAddress == null) {
                //throw error if still can't get memory.
                throw new IllegalStateException("Out Of Memory Error.");
            }
        }
        
        Element element = new Element(new ElementList());
        Sequence sequence = element.getListValue().getSequence();
        sequence.setUsesHeap(true);
        sequence.setMemoryAddress(memAddress);
        
        return element;
    }
}
